package log

import (
	"context"
	"fmt"
	"runtime"
	"sync/atomic"
	"time"

	sutil "gitee.com/simplexyz/simpleutil-go"
	sworker "gitee.com/simplexyz/simpleworker-go"
)

type ILogger interface {
	Destroy()
	SetLevel(level Level)
	Debug(args ...any)
	Debugf(format string, args ...any)
	Info(args ...any)
	Infof(format string, args ...any)
	Warn(args ...any)
	Warnf(format string, args ...any)
	Error(args ...any)
	Errorf(format string, args ...any)
}

type logger struct {
	*sworker.Worker

	LoggerOption

	consoleWriter *consoleWriter
	writers       []IWriter

	queue chan *record
}

func CreateLogger(option *LoggerOption, writers ...IWriter) (ILogger, error) {
	if option == nil {
		option = &LoggerOption{}
	}

	l := &logger{
		LoggerOption: *option,
	}

	var err error

	if l.Name == "" {
		l.Name, err = sutil.GetProgramFileBaseName()
		if err != nil {
			return nil, fmt.Errorf("init logger name fail, %w", err)
		}
	}

	if _, ok := GetLogger(l.Name); ok {
		return nil, fmt.Errorf("logger already exist")
	}

	if l.TimestampFormat == "" {
		l.TimestampFormat = "2006/01/02T15:04:05.000"
	}

	if l.QueueSize <= 0 {
		l.QueueSize = 1024
	}

	if l.Console {
		l.consoleWriter = createConsoleWriter(l.ConsoleColorful)
	}

	for _, w := range writers {
		if w != nil {
			l.writers = append(l.writers, w)
		}
	}

	l.queue = make(chan *record, l.QueueSize)

	l.Worker, err = sworker.Create(nil, true,
		sworker.WithOnWorking(l.onWorking),
	)
	if err == nil {
		loggers.Store(l.Name, l)
	}

	return l, err
}

func (l *logger) Destroy() {
	l.Worker.Stop(func() {
		close(l.queue)
	}, true)
}

func (l *logger) onWorking(ctx context.Context) bool {
	for {
		select {
		case <-ctx.Done():
			for r := range l.queue {
				l.write(r)
			}
			return false

		case r, ok := <-l.queue:
			if ok {
				l.write(r)
			}
		}
	}
}

func (l *logger) log(level Level, content string) {
	r := createRecord()

	r.timestamp = time.Now().Format(l.TimestampFormat)
	r.level = level
	r.prefix = l.Name
	r.content = content

	if l.Caller {
		var ok bool
		_, r.path, r.line, ok = runtime.Caller(l.CallerSkip)
		if !ok {
			r.path = "???"
			r.line = 0
		}
	}

	l.queue <- r
}

func (l *logger) write(r *record) {
	r.buildBytes()

	if l.consoleWriter != nil {
		l.consoleWriter.Write(r)
	}

	for i, w := range l.writers {
		if i == 0 {
			w.Write(r)
		} else {
			w.Write(r.Clone())
		}
	}

	if len(l.writers) == 0 {
		destroyRecord(r)
	}
}

func (l *logger) SetLevel(level Level) {
	if !level.Valid() {
		return
	}
	atomic.SwapInt32(&l.Level, int32(level))
}

func (l *logger) canOutput(level Level) bool {
	if !l.Working() {
		return false
	}
	if !level.Valid() {
		return false
	}
	if level < Level(atomic.LoadInt32(&l.Level)) {
		return false
	}
	return true
}

func (l *logger) Debug(args ...any) {
	if !l.canOutput(LevelDebug) {
		return
	}
	m := fmt.Sprint(args...)
	l.log(LevelDebug, m)
}

func (l *logger) Debugf(format string, args ...any) {
	if !l.canOutput(LevelDebug) {
		return
	}
	m := fmt.Sprintf(format, args...)
	l.log(LevelDebug, m)
}

func (l *logger) Info(args ...any) {
	if !l.canOutput(LevelInfo) {
		return
	}
	m := fmt.Sprint(args...)
	l.log(LevelInfo, m)
}

func (l *logger) Infof(format string, args ...any) {
	if !l.canOutput(LevelInfo) {
		return
	}
	m := fmt.Sprintf(format, args...)
	l.log(LevelInfo, m)
}

func (l *logger) Warn(args ...any) {
	if !l.canOutput(LevelWarn) {
		return
	}
	m := fmt.Sprint(args...)
	l.log(LevelWarn, m)
}

func (l *logger) Warnf(format string, args ...any) {
	if !l.canOutput(LevelWarn) {
		return
	}
	m := fmt.Sprintf(format, args...)
	l.log(LevelWarn, m)
}

func (l *logger) Error(args ...any) {
	if !l.canOutput(LevelError) {
		return
	}
	m := fmt.Sprint(args...)
	l.log(LevelError, m)
}

func (l *logger) Errorf(format string, args ...any) {
	if !l.canOutput(LevelError) {
		return
	}
	m := fmt.Sprintf(format, args...)
	l.log(LevelError, m)
}
